/**
 * Copyright (C) 2017 - 2020 https://github.com/joewee
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bifrost.workflow;

import com.bifrost.exception.RollbackFailureException;
import com.bifrost.exception.WorkflowException;
import com.bifrost.workflow.state.ActivityStateManager;
import com.bifrost.workflow.state.ActivityStateManagerImpl;
import com.bifrost.workflow.state.RollbackStateLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * @author joewee
 * @version 1.0.0
 * @Date 2017/11/14 18:08
 */
public class SequenceProcessor<U, T> extends BaseProcessor<U, T> {
    private static final Logger LOG = LoggerFactory.getLogger(SequenceProcessor.class);

    private ProcessContextFactory<U, T> processContextFactory;

    @Override
    public boolean supports(Activity<? extends ProcessContext<U>> activity) {
        return true;
    }

    @Override
    public <P extends ProcessContext<U>> P doActivities() throws WorkflowException {
        return doActivities(null);
    }

    @Override
    public <P extends ProcessContext<U>> P doActivities(T seedData) throws WorkflowException {
        if (LOG.isDebugEnabled()) {
            LOG.debug(getBeanName() + " processor is running..");
        }
        ActivityStateManager activityStateManager = getBeanFactory().getBean(ActivityStateManager.class, "activityStateManager");
        if (activityStateManager == null) {
            throw new IllegalStateException("Unable to find an instance of ActivityStateManager registered under bean id blActivityStateManager");
        }
        ProcessContext<U> context = null;

        RollbackStateLocal rollbackStateLocal = new RollbackStateLocal();
        rollbackStateLocal.setThreadId(String.valueOf(Thread.currentThread().getId()));
        rollbackStateLocal.setWorkflowId(getBeanName());
        RollbackStateLocal.setRollbackStateLocal(rollbackStateLocal);

        try {
            //retrieve injected by Spring
            List<Activity<ProcessContext<U>>> activities = getActivities();

            //retrieve a new instance of the Workflow ProcessContext
            context = createContext(seedData);

            for (Activity<ProcessContext<U>> activity : activities) {
                if (activity.shouldExecute(context)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("running activity:" + activity.getBeanName() + " using arguments:" + context);
                    }

                    try {
                        context = activity.execute(context);
                    } catch (Throwable activityException) {
                        RollbackFailureException rollbackFailure = null;
                        if (getAutoRollbackOnError()) {
                            LOG.info(String.format("exception ocurred in %s, executing rollback handlers", rollbackStateLocal.getWorkflowId()));

                            try {
                                ActivityStateManagerImpl.getStateManager().rollbackAllState();
                            } catch (Throwable rollbackException) {
                                LOG.error(String.format("There was an exception rolling back %s", rollbackStateLocal.getWorkflowId()), rollbackException);

                                if (rollbackException instanceof RollbackFailureException) {
                                    rollbackFailure = (RollbackFailureException) rollbackException;
                                } else {
                                    rollbackFailure = new RollbackFailureException(rollbackException);
                                }

                                LOG.error(String.format("The original cause of the rollback for %s was", rollbackStateLocal.getWorkflowId()), activityException);
                                rollbackFailure.setOriginalWorkflowException(activityException);
                                throw rollbackFailure;
                            }
                        }

                        ErrorHandler errorHandler = activity.getErrorHandler();
                        if (errorHandler == null) {
                            getDefaultErrorHandler().handleError(context, activityException);
                            break;
                        } else {
                            errorHandler.handleError(context, activityException);
                        }
                    }

                    //ensure its ok to continue the process
                    if (processShouldStop(context, activity)) {
                        break;
                    }

                    //register the RollbackHandler
                    if (activity.getRollbackHandler() != null && activity.getAutomaticallyRegisterRollbackHandler()) {
                        ActivityStateManagerImpl.getStateManager().registerState(activity, context, activity.getRollbackRegion(), activity.getRollbackHandler(), activity.getStateConfiguration());
                    }
                } else {
                    LOG.debug("Not executing activity: " + activity.getBeanName() + " based on the context: " + context);
                }
            }
        } finally {
            rollbackStateLocal = RollbackStateLocal.getRollbackStateLocal();
            if (rollbackStateLocal != null && rollbackStateLocal.getWorkflowId().equals(getBeanName())) {
                activityStateManager.clearAllState();
            }
        }
        LOG.debug(getBeanName() + " processor is done.");

        return (P) context;
    }

    /**
     * Determine if the process should stop
     *
     * @param context  the current process context
     * @param activity the current activity in the iteration
     */
    protected boolean processShouldStop(ProcessContext<U> context, Activity<ProcessContext<U>> activity) {
        if (context == null || context.isStopped()) {
            LOG.info("Interrupted workflow as requested by:" + activity.getBeanName());
            return true;
        }
        return false;
    }

    protected ProcessContext<U> createContext(T seedData) throws WorkflowException {
        return processContextFactory.createContext(seedData);
    }

    @Override
    public void setProcessContextFactory(ProcessContextFactory<U, T> processContextFactory) {
        this.processContextFactory = processContextFactory;
    }

}
